using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Nvigorate.Data;
using Nvigorate.Data.Query;
using Nvigorate.Extensions;
using Nvigorate.Relational.Mapping;
using Nvigorate.Relational.Mapping.Fluent;
using Nvigorate.Relational.Mapping.Index;
using Nvigorate.Relational.Mapping.Interface;
using Nvigorate.TestObjects;

namespace Nvigorate.Test.Relational.Binding
{
    /// <summary>
    /// Summary description for FluentMapTests
    /// </summary>
    [TestClass]
    public class FluentMapTests
    {
        public FluentMapTests()
        {
            //
            // TODO: Add constructor logic here
            //
        }

        private TestContext testContextInstance;

        /// <summary>
        ///Gets or sets the test context which provides
        ///information about and functionality for the current test run.
        ///</summary>
        public TestContext TestContext
        {
            get
            {
                return testContextInstance;
            }
            set
            {
                testContextInstance = value;
            }
        }

        #region Additional test attributes
        //
        // You can use the following additional attributes as you write your tests:
        //
        // Use ClassInitialize to run code before running the first test in the class
        // [ClassInitialize()]
        // public static void MyClassInitialize(TestContext testContext) { }
        //
        // Use ClassCleanup to run code after all tests in a class have run
        // [ClassCleanup()]
        // public static void MyClassCleanup() { }
        //
        // Use TestInitialize to run code before running each test 
        // [TestInitialize()]
        // public void MyTestInitialize() { }
        //
        // Use TestCleanup to run code after each test has run
        // [TestCleanup()]
        // public void MyTestCleanup() { }
        //
        #endregion

        [TestMethod]
        public void TestMapGeneration()
        {
            MapFake.PopulateMaps();
            var index = new MapIndex();
            var xmlMap = index["simple", typeof (Person)];

            var personBindingMap = new PersonMap().GetMap();

            Assert.AreEqual(xmlMap.RepositoryName, personBindingMap.RepositoryName);
            Assert.AreEqual(xmlMap.TargetName, personBindingMap.TargetName);
            Assert.AreEqual(xmlMap.TargetType, personBindingMap.TargetType);
            Assert.AreEqual(xmlMap.CanDelete, personBindingMap.CanDelete);
            Assert.AreEqual(xmlMap.CanInsert, personBindingMap.CanInsert);
            Assert.AreEqual(xmlMap.CanUpdate, personBindingMap.CanUpdate);
            Assert.AreEqual(xmlMap.CanSelect, personBindingMap.CanSelect);

            foreach (var relationship in xmlMap.Relationships)
            {
                var targetRelationship =
                    personBindingMap.Relationships.First(
                        r =>
                        r.RelativeProperty == relationship.RelativeProperty &&
                        r.RelativeType == relationship.RelativeType);

                Assert.AreEqual(targetRelationship.Relative, relationship.Relative);
                Assert.AreEqual(targetRelationship.Subject, relationship.Subject);
                Assert.AreEqual(targetRelationship.SubjectType, relationship.SubjectType);
                Assert.AreEqual(targetRelationship.Relationship, relationship.Relationship);

                if (relationship is ManyToManyRelationship)
                {
                    var m2m1 = relationship as ManyToManyRelationship;
                    var m2m2 = targetRelationship as ManyToManyRelationship;

                    Assert.AreEqual(m2m1.Container.ContainerName, m2m2.Container.ContainerName);
                    Assert.AreEqual(m2m1.Container.RelativePairs.Count, m2m2.Container.RelativePairs.Count);
                    Assert.AreEqual(m2m1.Container.SubjectPairs.Count, m2m2.Container.SubjectPairs.Count);

                    var i = 0;
                    m2m1.Container.SubjectPairs.ForEach(p =>
                        {
                            var pair = m2m2.Container.SubjectPairs[i++];
                            Assert.AreEqual(pair.Target, p.Target);
                            Assert.AreEqual(pair.Source, p.Source);
                        });
                    m2m1.Container.RelativePairs.ForEach(p =>
                        {
                            var pair = m2m2.Container.RelativePairs[i++];
                            Assert.AreEqual(pair.Target, p.Target);
                            Assert.AreEqual(pair.Source, p.Source);
                        });
                }
                else
                {
                    var one1 = relationship as OneToManyRelationship;
                    var one2 = targetRelationship as OneToManyRelationship;

                    one1.Constraints.ForEach(p =>
                        {
                            Assert.AreEqual(p.Value, one2.Constraints[p.Key]);
                        });
                }
            }
        }

        [TestMethod]
        public void TestSimpleAssignmentMapping()
        {
            var source = new FluentSource<Person>("Person");
            var fluentAssignment = source.Assign(p => p.FirstName);
            var assignment = fluentAssignment.GetAssignment();

            Assert.AreEqual(false, assignment.Nullable);
            Assert.AreEqual(true, assignment.Persistable);

            Assert.AreEqual("Person", assignment.Source.Entity);
            Assert.AreEqual("FirstName", assignment.Source.Field);
            Assert.AreEqual(false, assignment.PrimaryKey);

            Assert.AreEqual("Nvigorate.TestObjects.Person, Nvigorate.TestObjects", assignment.Target.Entity);
            Assert.AreEqual("FirstName", assignment.Target.Field);
            Assert.AreEqual(false, assignment.PrimaryKey);
        }

        [TestMethod]
        public void TestComplexAssignmentMapping()
        {
            var source = new FluentSource<Person>("Person");
            var fluentAssignment = source.Assign(p => p.FirstName).AsKey().Nullable().ReadOnly().ToColumn("test");
            var assignment = fluentAssignment.GetAssignment();

            Assert.AreEqual(true, assignment.Nullable);
            Assert.AreEqual(false, assignment.Persistable);

            Assert.AreEqual("Person", assignment.Source.Entity);
            Assert.AreEqual("test", assignment.Source.Field);
            Assert.AreEqual(true, assignment.PrimaryKey);

            Assert.AreEqual("Nvigorate.TestObjects.Person, Nvigorate.TestObjects", assignment.Target.Entity);
            Assert.AreEqual("FirstName", assignment.Target.Field);
            Assert.AreEqual(true, assignment.PrimaryKey);
        }

        [TestMethod]
        public void TestSourceMapGeneration()
        {
            var source = new FluentSource<Person>("Person");
            var childSource = new FluentSource<Person>("PersonAddress");
            
            source.Assign(p => p.FirstName);
            source.Assign(p => p.LastName);
            source.Assign(p => p.DateOfBirth);
            source.Assign(p => p.SocialSecurity).AsKey();

            childSource.JoinTo(source, JoinType.Left,
                               childSource.PropertyCriteria(p => p.SocialSecurity).EqualTo(source.Column("Social"))
                );

            var map = source.GetSourceMap();
            var childMap = childSource.GetSourceMap();

            Assert.AreEqual("Person", map.SourceName);
            Assert.IsNull(map.Join);

            Assert.AreEqual("Person", childMap.Join.Relative);
            Assert.AreEqual("PersonAddress", childMap.Join.Subject);

            SearchCriteria equalTo = new DomainCriterion<Person>().Property(p => p.SocialSecurity).EqualTo(Column.Named("Social").OwnedBy("Person"));
            var joinOnText = childMap.Join.JoinCriteria.Translate(new MSSQL9Provider());
            var criteriaText = equalTo.Translate(new MSSQL9Provider());

            Assert.AreEqual(joinOnText, criteriaText);

            var firstName = map.Assignments.First(a => a.Target.Field == "FirstName");
            var lastName = map.Assignments.First(a => a.Source.Field == "LastName");
            var dateOfBirth = map.Assignments.First(a => a.Source.Field == "DateOfBirth");
            var socialSecurity = map.Assignments.First(a => a.Source.Field == "SocialSecurity");
            Assert.IsTrue(socialSecurity.PrimaryKey);
        }

        [TestMethod]
        public void TestManyToManyRelationshipMapping()
        {
            var map = new FluentMap<Author>();
            map.RepositoryName = "simple";
            var fluentManyToMany = map.MapToMany(a => a.Contributions, "contributor")
                .SelfToContainer(a => a.Id, "authorId")
                .RelativeToContainer(e => e.Id, "entryId");

            var relationship = fluentManyToMany.GetRelationship();

            Assert.AreEqual(Relationship.Peer, relationship.Relationship);
            Assert.AreEqual("Nvigorate.TestObjects.Entry, Nvigorate.TestObjects", relationship.Relative);
            Assert.AreEqual("Contributions", relationship.RelativeProperty);
            Assert.AreEqual(typeof(Entry), relationship.RelativeType);
            Assert.AreEqual("Nvigorate.TestObjects.Author, Nvigorate.TestObjects", relationship.Subject);
            Assert.AreEqual(typeof(Author), relationship.SubjectType);

            var manyToMany = relationship as ManyToManyRelationship;
            Assert.AreEqual("contributor", manyToMany.Container.ContainerName );
            
            Assert.AreEqual("entryId", manyToMany.Container.RelativePairs[0].Source);
            Assert.AreEqual("Id", manyToMany.Container.RelativePairs[0].Target);
            Assert.AreEqual(RelationPairType.Relative, manyToMany.Container.RelativePairs[0].PairType);
            
            Assert.AreEqual("authorId", manyToMany.Container.SubjectPairs[0].Source);
            Assert.AreEqual("Id", manyToMany.Container.SubjectPairs[0].Target);
            Assert.AreEqual(RelationPairType.Subject, manyToMany.Container.SubjectPairs[0].PairType);
        }

        [TestMethod]
        public void TestOneToManyRelationshipMapping()
        {
            var map = new FluentMap<Person>();
            map.RepositoryName = "simple";
            var fluentOneToMany = map.MapChildren(p => p.Vehicles)
                .ForeignKey(p => p.SocialSecurity, v => v.OwnerSocial);

            var relationship = fluentOneToMany.GetRelationship();

            Assert.AreEqual(Relationship.Parent, relationship.Relationship);
            Assert.AreEqual("Nvigorate.TestObjects.Vehicle, Nvigorate.TestObjects", relationship.Relative);
            Assert.AreEqual("Vehicles", relationship.RelativeProperty);
            Assert.AreEqual(typeof(Vehicle), relationship.RelativeType);
            Assert.AreEqual("Nvigorate.TestObjects.Person, Nvigorate.TestObjects", relationship.Subject);
            Assert.AreEqual(typeof(Person), relationship.SubjectType);

            var oneToMany = relationship as OneToManyRelationship;
            Assert.AreEqual("OwnerSocial", oneToMany.Constraints["SocialSecurity"]);
        }

    }
}
